/*
 *  Copyright 2006 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.mybatis.generator.api;

import static org.mybatis.generator.internal.util.StringUtility.isTrue;
import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.GeneratedKey;
import org.mybatis.generator.config.JavaClientGeneratorConfiguration;
import org.mybatis.generator.config.JavaModelGeneratorConfiguration;
import org.mybatis.generator.config.ModelType;
import org.mybatis.generator.config.PropertyHolder;
import org.mybatis.generator.config.PropertyRegistry;
import org.mybatis.generator.config.SqlMapGeneratorConfiguration;
import org.mybatis.generator.config.TableConfiguration;
import org.mybatis.generator.internal.rules.ConditionalModelRules;
import org.mybatis.generator.internal.rules.FlatModelRules;
import org.mybatis.generator.internal.rules.HierarchicalModelRules;
import org.mybatis.generator.internal.rules.Rules;

/**
 * Base class for all code generator implementations. This class provides many of the housekeeping methods needed to
 * implement a code generator, with only the actual code generation methods left unimplemented.
 * 
 * @author Jeff Butler
 * 
 */
public abstract class IntrospectedTable
{
    public enum TargetRuntime
    {
        IBATIS2, MYBATIS3
    }
    
    protected enum InternalAttribute
    {
        ATTR_DAO_IMPLEMENTATION_TYPE, ATTR_DAO_INTERFACE_TYPE, ATTR_PRIMARY_KEY_TYPE, ATTR_BASE_RECORD_TYPE, ATTR_RECORD_WITH_BLOBS_TYPE, ATTR_EXAMPLE_TYPE, ATTR_IBATIS2_SQL_MAP_PACKAGE, ATTR_IBATIS2_SQL_MAP_FILE_NAME, ATTR_IBATIS2_SQL_MAP_NAMESPACE, ATTR_MYBATIS3_XML_MAPPER_PACKAGE, ATTR_MYBATIS3_XML_MAPPER_FILE_NAME,
        /** also used as XML Mapper namespace if a Java mapper is generated */
        ATTR_MYBATIS3_JAVA_MAPPER_TYPE,
        /** used as XML Mapper namespace if no client is generated */
        ATTR_MYBATIS3_FALLBACK_SQL_MAP_NAMESPACE, ATTR_FULLY_QUALIFIED_TABLE_NAME_AT_RUNTIME, ATTR_ALIASED_FULLY_QUALIFIED_TABLE_NAME_AT_RUNTIME, ATTR_COUNT_BY_EXAMPLE_STATEMENT_ID, ATTR_DELETE_BY_EXAMPLE_STATEMENT_ID, ATTR_DELETE_BY_PRIMARY_KEY_STATEMENT_ID, ATTR_INSERT_STATEMENT_ID, ATTR_INSERT_SELECTIVE_STATEMENT_ID, ATTR_SELECT_ALL_STATEMENT_ID, ATTR_SELECT_BY_EXAMPLE_STATEMENT_ID, ATTR_SELECT_BY_EXAMPLE_WITH_BLOBS_STATEMENT_ID, ATTR_SELECT_BY_PRIMARY_KEY_STATEMENT_ID, ATTR_UPDATE_BY_EXAMPLE_STATEMENT_ID, ATTR_UPDATE_BY_EXAMPLE_SELECTIVE_STATEMENT_ID, ATTR_UPDATE_BY_EXAMPLE_WITH_BLOBS_STATEMENT_ID, ATTR_UPDATE_BY_PRIMARY_KEY_STATEMENT_ID, ATTR_UPDATE_BY_PRIMARY_KEY_SELECTIVE_STATEMENT_ID, ATTR_UPDATE_BY_PRIMARY_KEY_WITH_BLOBS_STATEMENT_ID, ATTR_BASE_RESULT_MAP_ID, ATTR_RESULT_MAP_WITH_BLOBS_ID, ATTR_EXAMPLE_WHERE_CLAUSE_ID, ATTR_BASE_COLUMN_LIST_ID, ATTR_BLOB_COLUMN_LIST_ID, ATTR_MYBATIS3_UPDATE_BY_EXAMPLE_WHERE_CLAUSE_ID, ATTR_MYBATIS3_SQL_PROVIDER_TYPE
    }
    
    protected TableConfiguration tableConfiguration;
    
    protected FullyQualifiedTable fullyQualifiedTable;
    
    protected Context context;
    
    protected Rules rules;
    
    protected List<IntrospectedColumn> primaryKeyColumns;
    
    protected List<IntrospectedColumn> baseColumns;
    
    protected List<IntrospectedColumn> blobColumns;
    
    protected TargetRuntime targetRuntime;
    
    /**
     * Attributes may be used by plugins to capture table related state between the different plugin calls.
     */
    protected Map<String, Object> attributes;
    
    /**
     * Internal attributes are used to store commonly accessed items by all code generators
     */
    protected Map<IntrospectedTable.InternalAttribute, String> internalAttributes;
    
    public IntrospectedTable(TargetRuntime targetRuntime)
    {
        super();
        this.targetRuntime = targetRuntime;
        primaryKeyColumns = new ArrayList<IntrospectedColumn>();
        baseColumns = new ArrayList<IntrospectedColumn>();
        blobColumns = new ArrayList<IntrospectedColumn>();
        attributes = new HashMap<String, Object>();
        internalAttributes = new HashMap<IntrospectedTable.InternalAttribute, String>();
    }
    
    public FullyQualifiedTable getFullyQualifiedTable()
    {
        return fullyQualifiedTable;
    }
    
    public String getSelectByExampleQueryId()
    {
        return tableConfiguration.getSelectByExampleQueryId();
    }
    
    public String getSelectByPrimaryKeyQueryId()
    {
        return tableConfiguration.getSelectByPrimaryKeyQueryId();
    }
    
    public GeneratedKey getGeneratedKey()
    {
        return tableConfiguration.getGeneratedKey();
    }
    
    public IntrospectedColumn getColumn(String columnName)
    {
        if (columnName == null)
        {
            return null;
        }
        else
        {
            // search primary key columns
            for (IntrospectedColumn introspectedColumn : primaryKeyColumns)
            {
                if (introspectedColumn.isColumnNameDelimited())
                {
                    if (introspectedColumn.getActualColumnName().equals(columnName))
                    {
                        return introspectedColumn;
                    }
                }
                else
                {
                    if (introspectedColumn.getActualColumnName().equalsIgnoreCase(columnName))
                    {
                        return introspectedColumn;
                    }
                }
            }
            
            // search base columns
            for (IntrospectedColumn introspectedColumn : baseColumns)
            {
                if (introspectedColumn.isColumnNameDelimited())
                {
                    if (introspectedColumn.getActualColumnName().equals(columnName))
                    {
                        return introspectedColumn;
                    }
                }
                else
                {
                    if (introspectedColumn.getActualColumnName().equalsIgnoreCase(columnName))
                    {
                        return introspectedColumn;
                    }
                }
            }
            
            // search blob columns
            for (IntrospectedColumn introspectedColumn : blobColumns)
            {
                if (introspectedColumn.isColumnNameDelimited())
                {
                    if (introspectedColumn.getActualColumnName().equals(columnName))
                    {
                        return introspectedColumn;
                    }
                }
                else
                {
                    if (introspectedColumn.getActualColumnName().equalsIgnoreCase(columnName))
                    {
                        return introspectedColumn;
                    }
                }
            }
            
            return null;
        }
    }
    
    /**
     * Returns true if any of the columns in the table are JDBC Dates (as opposed to timestamps).
     * 
     * @return true if the table contains DATE columns
     */
    public boolean hasJDBCDateColumns()
    {
        boolean rc = false;
        
        for (IntrospectedColumn introspectedColumn : primaryKeyColumns)
        {
            if (introspectedColumn.isJDBCDateColumn())
            {
                rc = true;
                break;
            }
        }
        
        if (!rc)
        {
            for (IntrospectedColumn introspectedColumn : baseColumns)
            {
                if (introspectedColumn.isJDBCDateColumn())
                {
                    rc = true;
                    break;
                }
            }
        }
        
        return rc;
    }
    
    /**
     * Returns true if any of the columns in the table are JDBC Times (as opposed to timestamps).
     * 
     * @return true if the table contains TIME columns
     */
    public boolean hasJDBCTimeColumns()
    {
        boolean rc = false;
        
        for (IntrospectedColumn introspectedColumn : primaryKeyColumns)
        {
            if (introspectedColumn.isJDBCTimeColumn())
            {
                rc = true;
                break;
            }
        }
        
        if (!rc)
        {
            for (IntrospectedColumn introspectedColumn : baseColumns)
            {
                if (introspectedColumn.isJDBCTimeColumn())
                {
                    rc = true;
                    break;
                }
            }
        }
        
        return rc;
    }
    
    /**
     * Returns the columns in the primary key. If the generatePrimaryKeyClass() method returns false, then these columns
     * will be iterated as the parameters of the selectByPrimaryKay and deleteByPrimaryKey methods
     * 
     * @return a List of ColumnDefinition objects for columns in the primary key
     */
    public List<IntrospectedColumn> getPrimaryKeyColumns()
    {
        return primaryKeyColumns;
    }
    
    public boolean hasPrimaryKeyColumns()
    {
        return primaryKeyColumns.size() > 0;
    }
    
    public List<IntrospectedColumn> getBaseColumns()
    {
        return baseColumns;
    }
    
    /**
     * Returns all columns in the table (for use by the select by primary key and select by example with BLOBs methods)
     * 
     * @return a List of ColumnDefinition objects for all columns in the table
     */
    public List<IntrospectedColumn> getAllColumns()
    {
        List<IntrospectedColumn> answer = new ArrayList<IntrospectedColumn>();
        answer.addAll(primaryKeyColumns);
        answer.addAll(baseColumns);
        answer.addAll(blobColumns);
        
        return answer;
    }
    
    /**
     * Returns all columns except BLOBs (for use by the select by example without BLOBs method)
     * 
     * @return a List of ColumnDefinition objects for columns in the table that are non BLOBs
     */
    public List<IntrospectedColumn> getNonBLOBColumns()
    {
        List<IntrospectedColumn> answer = new ArrayList<IntrospectedColumn>();
        answer.addAll(primaryKeyColumns);
        answer.addAll(baseColumns);
        
        return answer;
    }
    
    public int getNonBLOBColumnCount()
    {
        return primaryKeyColumns.size() + baseColumns.size();
    }
    
    public List<IntrospectedColumn> getNonPrimaryKeyColumns()
    {
        List<IntrospectedColumn> answer = new ArrayList<IntrospectedColumn>();
        answer.addAll(baseColumns);
        answer.addAll(blobColumns);
        
        return answer;
    }
    
    public List<IntrospectedColumn> getBLOBColumns()
    {
        return blobColumns;
    }
    
    public boolean hasBLOBColumns()
    {
        return blobColumns.size() > 0;
    }
    
    public boolean hasBaseColumns()
    {
        return baseColumns.size() > 0;
    }
    
    public Rules getRules()
    {
        return rules;
    }
    
    public String getTableConfigurationProperty(String property)
    {
        return tableConfiguration.getProperty(property);
    }
    
    public String getPrimaryKeyType()
    {
        return internalAttributes.get(InternalAttribute.ATTR_PRIMARY_KEY_TYPE);
    }
    
    /**
     * 
     * @return the type for the record (the class that holds non-primary key and non-BLOB fields). Note that the value
     *         will be calculated regardless of whether the table has these columns or not.
     */
    public String getBaseRecordType()
    {
        return internalAttributes.get(InternalAttribute.ATTR_BASE_RECORD_TYPE);
    }
    
    /**
     * 
     * @return the type for the example class.
     */
    public String getExampleType()
    {
        return internalAttributes.get(InternalAttribute.ATTR_EXAMPLE_TYPE);
    }
    
    /**
     * 
     * @return the type for the record with BLOBs class. Note that the value will be calculated regardless of whether
     *         the table has BLOB columns or not.
     */
    public String getRecordWithBLOBsType()
    {
        return internalAttributes.get(InternalAttribute.ATTR_RECORD_WITH_BLOBS_TYPE);
    }
    
    /**
     * Calculates an SQL Map file name for the table. Typically the name is "XXXX_SqlMap.xml" where XXXX is the fully
     * qualified table name (delimited with underscores).
     * 
     * @return the name of the SqlMap file
     */
    public String getIbatis2SqlMapFileName()
    {
        return internalAttributes.get(InternalAttribute.ATTR_IBATIS2_SQL_MAP_FILE_NAME);
    }
    
    public String getIbatis2SqlMapNamespace()
    {
        return internalAttributes.get(InternalAttribute.ATTR_IBATIS2_SQL_MAP_NAMESPACE);
    }
    
    public String getMyBatis3SqlMapNamespace()
    {
        String namespace = getMyBatis3JavaMapperType();
        if (namespace == null)
        {
            namespace = getMyBatis3FallbackSqlMapNamespace();
        }
        
        return namespace;
    }
    
    public String getMyBatis3FallbackSqlMapNamespace()
    {
        return internalAttributes.get(InternalAttribute.ATTR_MYBATIS3_FALLBACK_SQL_MAP_NAMESPACE);
    }
    
    /**
     * Calculates the package for the current table.
     * 
     * @return the package for the SqlMap for the current table
     */
    public String getIbatis2SqlMapPackage()
    {
        return internalAttributes.get(InternalAttribute.ATTR_IBATIS2_SQL_MAP_PACKAGE);
    }
    
    public String getDAOImplementationType()
    {
        return internalAttributes.get(InternalAttribute.ATTR_DAO_IMPLEMENTATION_TYPE);
    }
    
    public String getDAOInterfaceType()
    {
        return internalAttributes.get(InternalAttribute.ATTR_DAO_INTERFACE_TYPE);
    }
    
    public boolean hasAnyColumns()
    {
        return primaryKeyColumns.size() > 0 || baseColumns.size() > 0 || blobColumns.size() > 0;
    }
    
    public void setTableConfiguration(TableConfiguration tableConfiguration)
    {
        this.tableConfiguration = tableConfiguration;
    }
    
    public void setFullyQualifiedTable(FullyQualifiedTable fullyQualifiedTable)
    {
        this.fullyQualifiedTable = fullyQualifiedTable;
    }
    
    public void setContext(Context context)
    {
        this.context = context;
    }
    
    public void addColumn(IntrospectedColumn introspectedColumn)
    {
        if (introspectedColumn.isBLOBColumn())
        {
            blobColumns.add(introspectedColumn);
        }
        else
        {
            baseColumns.add(introspectedColumn);
        }
        
        introspectedColumn.setIntrospectedTable(this);
    }
    
    public void addPrimaryKeyColumn(String columnName)
    {
        boolean found = false;
        // first search base columns
        Iterator<IntrospectedColumn> iter = baseColumns.iterator();
        while (iter.hasNext())
        {
            IntrospectedColumn introspectedColumn = iter.next();
            if (introspectedColumn.getActualColumnName().equals(columnName))
            {
                primaryKeyColumns.add(introspectedColumn);
                iter.remove();
                found = true;
                break;
            }
        }
        
        // search blob columns in the weird event that a blob is the primary key
        if (!found)
        {
            iter = blobColumns.iterator();
            while (iter.hasNext())
            {
                IntrospectedColumn introspectedColumn = iter.next();
                if (introspectedColumn.getActualColumnName().equals(columnName))
                {
                    primaryKeyColumns.add(introspectedColumn);
                    iter.remove();
                    found = true;
                    break;
                }
            }
        }
    }
    
    public Object getAttribute(String name)
    {
        return attributes.get(name);
    }
    
    public void removeAttribute(String name)
    {
        attributes.remove(name);
    }
    
    public void setAttribute(String name, Object value)
    {
        attributes.put(name, value);
    }
    
    public void initialize()
    {
        calculateJavaClientAttributes();
        calculateModelAttributes();
        calculateXmlAttributes();
        
        if (tableConfiguration.getModelType() == ModelType.HIERARCHICAL)
        {
            rules = new HierarchicalModelRules(this);
        }
        else if (tableConfiguration.getModelType() == ModelType.FLAT)
        {
            rules = new FlatModelRules(this);
        }
        else
        {
            rules = new ConditionalModelRules(this);
        }
        
        context.getPlugins().initialized(this);
    }
    
    /**
     * 
     */
    protected void calculateXmlAttributes()
    {
        setIbatis2SqlMapPackage(calculateSqlMapPackage());
        setIbatis2SqlMapFileName(calculateIbatis2SqlMapFileName());
        setMyBatis3XmlMapperFileName(calculateMyBatis3XmlMapperFileName());
        setMyBatis3XmlMapperPackage(calculateSqlMapPackage());
        
        setIbatis2SqlMapNamespace(calculateIbatis2SqlMapNamespace());
        setMyBatis3FallbackSqlMapNamespace(calculateMyBatis3FallbackSqlMapNamespace());
        
        setSqlMapFullyQualifiedRuntimeTableName(calculateSqlMapFullyQualifiedRuntimeTableName());
        setSqlMapAliasedFullyQualifiedRuntimeTableName(calculateSqlMapAliasedFullyQualifiedRuntimeTableName());
        
        setCountByExampleStatementId("countByExample"); //$NON-NLS-1$
        setDeleteByExampleStatementId("deleteByExample"); //$NON-NLS-1$
        setDeleteByPrimaryKeyStatementId("deleteByPrimaryKey"); //$NON-NLS-1$
        setInsertStatementId("insert"); //$NON-NLS-1$
        setInsertSelectiveStatementId("insertSelective"); //$NON-NLS-1$
        setSelectAllStatementId("selectAll"); //$NON-NLS-1$
        setSelectByExampleStatementId("selectByExample"); //$NON-NLS-1$
        setSelectByExampleWithBLOBsStatementId("selectByExampleWithBLOBs"); //$NON-NLS-1$
        setSelectByPrimaryKeyStatementId("selectByPrimaryKey"); //$NON-NLS-1$
        setUpdateByExampleStatementId("updateByExample"); //$NON-NLS-1$
        setUpdateByExampleSelectiveStatementId("updateByExampleSelective"); //$NON-NLS-1$
        setUpdateByExampleWithBLOBsStatementId("updateByExampleWithBLOBs"); //$NON-NLS-1$
        setUpdateByPrimaryKeyStatementId("updateByPrimaryKey"); //$NON-NLS-1$
        setUpdateByPrimaryKeySelectiveStatementId("updateByPrimaryKeySelective"); //$NON-NLS-1$
        setUpdateByPrimaryKeyWithBLOBsStatementId("updateByPrimaryKeyWithBLOBs"); //$NON-NLS-1$
        setBaseResultMapId("BaseResultMap"); //$NON-NLS-1$
        setResultMapWithBLOBsId("ResultMapWithBLOBs"); //$NON-NLS-1$
        setExampleWhereClauseId("Example_Where_Clause"); //$NON-NLS-1$
        setBaseColumnListId("Base_Column_List"); //$NON-NLS-1$
        setBlobColumnListId("Blob_Column_List"); //$NON-NLS-1$
        setMyBatis3UpdateByExampleWhereClauseId("Update_By_Example_Where_Clause"); //$NON-NLS-1$
    }
    
    public void setBlobColumnListId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_BLOB_COLUMN_LIST_ID, s);
    }
    
    public void setBaseColumnListId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_BASE_COLUMN_LIST_ID, s);
    }
    
    public void setExampleWhereClauseId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_EXAMPLE_WHERE_CLAUSE_ID, s);
    }
    
    public void setMyBatis3UpdateByExampleWhereClauseId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_MYBATIS3_UPDATE_BY_EXAMPLE_WHERE_CLAUSE_ID, s);
    }
    
    public void setResultMapWithBLOBsId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_RESULT_MAP_WITH_BLOBS_ID, s);
    }
    
    public void setBaseResultMapId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_BASE_RESULT_MAP_ID, s);
    }
    
    public void setUpdateByPrimaryKeyWithBLOBsStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_UPDATE_BY_PRIMARY_KEY_WITH_BLOBS_STATEMENT_ID, s);
    }
    
    public void setUpdateByPrimaryKeySelectiveStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_UPDATE_BY_PRIMARY_KEY_SELECTIVE_STATEMENT_ID, s);
    }
    
    public void setUpdateByPrimaryKeyStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_UPDATE_BY_PRIMARY_KEY_STATEMENT_ID, s);
    }
    
    public void setUpdateByExampleWithBLOBsStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_UPDATE_BY_EXAMPLE_WITH_BLOBS_STATEMENT_ID, s);
    }
    
    public void setUpdateByExampleSelectiveStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_UPDATE_BY_EXAMPLE_SELECTIVE_STATEMENT_ID, s);
    }
    
    public void setUpdateByExampleStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_UPDATE_BY_EXAMPLE_STATEMENT_ID, s);
    }
    
    public void setSelectByPrimaryKeyStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_SELECT_BY_PRIMARY_KEY_STATEMENT_ID, s);
    }
    
    public void setSelectByExampleWithBLOBsStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_SELECT_BY_EXAMPLE_WITH_BLOBS_STATEMENT_ID, s);
    }
    
    public void setSelectAllStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_SELECT_ALL_STATEMENT_ID, s);
    }
    
    public void setSelectByExampleStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_SELECT_BY_EXAMPLE_STATEMENT_ID, s);
    }
    
    public void setInsertSelectiveStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_INSERT_SELECTIVE_STATEMENT_ID, s);
    }
    
    public void setInsertStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_INSERT_STATEMENT_ID, s);
    }
    
    public void setDeleteByPrimaryKeyStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_DELETE_BY_PRIMARY_KEY_STATEMENT_ID, s);
    }
    
    public void setDeleteByExampleStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_DELETE_BY_EXAMPLE_STATEMENT_ID, s);
    }
    
    public void setCountByExampleStatementId(String s)
    {
        internalAttributes.put(InternalAttribute.ATTR_COUNT_BY_EXAMPLE_STATEMENT_ID, s);
    }
    
    public String getBlobColumnListId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_BLOB_COLUMN_LIST_ID);
    }
    
    public String getBaseColumnListId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_BASE_COLUMN_LIST_ID);
    }
    
    public String getExampleWhereClauseId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_EXAMPLE_WHERE_CLAUSE_ID);
    }
    
    public String getMyBatis3UpdateByExampleWhereClauseId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_MYBATIS3_UPDATE_BY_EXAMPLE_WHERE_CLAUSE_ID);
    }
    
    public String getResultMapWithBLOBsId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_RESULT_MAP_WITH_BLOBS_ID);
    }
    
    public String getBaseResultMapId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_BASE_RESULT_MAP_ID);
    }
    
    public String getUpdateByPrimaryKeyWithBLOBsStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_UPDATE_BY_PRIMARY_KEY_WITH_BLOBS_STATEMENT_ID);
    }
    
    public String getUpdateByPrimaryKeySelectiveStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_UPDATE_BY_PRIMARY_KEY_SELECTIVE_STATEMENT_ID);
    }
    
    public String getUpdateByPrimaryKeyStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_UPDATE_BY_PRIMARY_KEY_STATEMENT_ID);
    }
    
    public String getUpdateByExampleWithBLOBsStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_UPDATE_BY_EXAMPLE_WITH_BLOBS_STATEMENT_ID);
    }
    
    public String getUpdateByExampleSelectiveStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_UPDATE_BY_EXAMPLE_SELECTIVE_STATEMENT_ID);
    }
    
    public String getUpdateByExampleStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_UPDATE_BY_EXAMPLE_STATEMENT_ID);
    }
    
    public String getSelectByPrimaryKeyStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_SELECT_BY_PRIMARY_KEY_STATEMENT_ID);
    }
    
    public String getSelectByExampleWithBLOBsStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_SELECT_BY_EXAMPLE_WITH_BLOBS_STATEMENT_ID);
    }
    
    public String getSelectAllStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_SELECT_ALL_STATEMENT_ID);
    }
    
    public String getSelectByExampleStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_SELECT_BY_EXAMPLE_STATEMENT_ID);
    }
    
    public String getInsertSelectiveStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_INSERT_SELECTIVE_STATEMENT_ID);
    }
    
    public String getInsertStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_INSERT_STATEMENT_ID);
    }
    
    public String getDeleteByPrimaryKeyStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_DELETE_BY_PRIMARY_KEY_STATEMENT_ID);
    }
    
    public String getDeleteByExampleStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_DELETE_BY_EXAMPLE_STATEMENT_ID);
    }
    
    public String getCountByExampleStatementId()
    {
        return internalAttributes.get(InternalAttribute.ATTR_COUNT_BY_EXAMPLE_STATEMENT_ID);
    }
    
    protected String calculateJavaClientImplementationPackage()
    {
        JavaClientGeneratorConfiguration config = context.getJavaClientGeneratorConfiguration();
        if (config == null)
        {
            return null;
        }
        
        StringBuilder sb = new StringBuilder();
        if (stringHasValue(config.getImplementationPackage()))
        {
            sb.append(config.getImplementationPackage());
        }
        else
        {
            sb.append(config.getTargetPackage());
        }
        
        sb.append(fullyQualifiedTable.getSubPackage(isSubPackagesEnabled(config)));
        
        return sb.toString();
    }
    
    private boolean isSubPackagesEnabled(PropertyHolder propertyHolder)
    {
        return isTrue(propertyHolder.getProperty(PropertyRegistry.ANY_ENABLE_SUB_PACKAGES));
    }
    
    protected String calculateJavaClientInterfacePackage()
    {
        JavaClientGeneratorConfiguration config = context.getJavaClientGeneratorConfiguration();
        if (config == null)
        {
            return null;
        }
        
        StringBuilder sb = new StringBuilder();
        sb.append(config.getTargetPackage());
        
        sb.append(fullyQualifiedTable.getSubPackage(isSubPackagesEnabled(config)));
        
        return sb.toString();
    }
    
    protected void calculateJavaClientAttributes()
    {
        if (context.getJavaClientGeneratorConfiguration() == null)
        {
            return;
        }
        
        StringBuilder sb = new StringBuilder();
        sb.append(calculateJavaClientImplementationPackage());
        sb.append('.');
        sb.append(fullyQualifiedTable.getDomainObjectName());
        sb.append("DAOImpl"); //$NON-NLS-1$
        setDAOImplementationType(sb.toString());
        
        sb.setLength(0);
        sb.append(calculateJavaClientInterfacePackage());
        sb.append('.');
        sb.append(fullyQualifiedTable.getDomainObjectName());
        sb.append("DAO"); //$NON-NLS-1$
        setDAOInterfaceType(sb.toString());
        
        sb.setLength(0);
        sb.append(calculateJavaClientInterfacePackage());
        sb.append('.');
        sb.append(fullyQualifiedTable.getDomainObjectName());
        sb.append("Mapper"); //$NON-NLS-1$
        setMyBatis3JavaMapperType(sb.toString());
        
        sb.setLength(0);
        sb.append(calculateJavaClientInterfacePackage());
        sb.append('.');
        sb.append(fullyQualifiedTable.getDomainObjectName());
        sb.append("SqlProvider"); //$NON-NLS-1$
        setMyBatis3SqlProviderType(sb.toString());
    }
    
    protected String calculateJavaModelPackage()
    {
        JavaModelGeneratorConfiguration config = context.getJavaModelGeneratorConfiguration();
        
        StringBuilder sb = new StringBuilder();
        sb.append(config.getTargetPackage());
        sb.append(fullyQualifiedTable.getSubPackage(isSubPackagesEnabled(config)));
        
        return sb.toString();
    }
    
    protected void calculateModelAttributes()
    {
        String pakkage = calculateJavaModelPackage();
        
        StringBuilder sb = new StringBuilder();
        sb.append(pakkage);
        sb.append('.');
        sb.append(fullyQualifiedTable.getDomainObjectName());
        sb.append("Key"); //$NON-NLS-1$
        setPrimaryKeyType(sb.toString());
        
        sb.setLength(0);
        sb.append(pakkage);
        sb.append('.');
        sb.append(fullyQualifiedTable.getDomainObjectName());
        setBaseRecordType(sb.toString());
        
        sb.setLength(0);
        sb.append(pakkage);
        sb.append('.');
        sb.append(fullyQualifiedTable.getDomainObjectName());
        sb.append("WithBLOBs"); //$NON-NLS-1$
        setRecordWithBLOBsType(sb.toString());
        
        sb.setLength(0);
        sb.append(pakkage);
        sb.append('.');
        sb.append(fullyQualifiedTable.getDomainObjectName());
        sb.append("Example"); //$NON-NLS-1$
        setExampleType(sb.toString());
    }
    
    protected String calculateSqlMapPackage()
    {
        StringBuilder sb = new StringBuilder();
        SqlMapGeneratorConfiguration config = context.getSqlMapGeneratorConfiguration();
        
        // config can be null if the Java client does not require XML
        if (config != null)
        {
            sb.append(config.getTargetPackage());
            sb.append(fullyQualifiedTable.getSubPackage(isSubPackagesEnabled(config)));
        }
        
        return sb.toString();
    }
    
    protected String calculateIbatis2SqlMapFileName()
    {
        StringBuilder sb = new StringBuilder();
        sb.append(fullyQualifiedTable.getIbatis2SqlMapNamespace());
        sb.append("_SqlMap.xml"); //$NON-NLS-1$
        return sb.toString();
    }
    
    protected String calculateMyBatis3XmlMapperFileName()
    {
        StringBuilder sb = new StringBuilder();
        sb.append(fullyQualifiedTable.getDomainObjectName());
        sb.append("Mapper.xml"); //$NON-NLS-1$
        return sb.toString();
    }
    
    protected String calculateIbatis2SqlMapNamespace()
    {
        return fullyQualifiedTable.getIbatis2SqlMapNamespace();
    }
    
    protected String calculateMyBatis3FallbackSqlMapNamespace()
    {
        StringBuilder sb = new StringBuilder();
        sb.append(calculateSqlMapPackage());
        sb.append('.');
        sb.append(fullyQualifiedTable.getDomainObjectName());
        sb.append("Mapper"); //$NON-NLS-1$
        return sb.toString();
    }
    
    protected String calculateSqlMapFullyQualifiedRuntimeTableName()
    {
        return fullyQualifiedTable.getFullyQualifiedTableNameAtRuntime();
    }
    
    protected String calculateSqlMapAliasedFullyQualifiedRuntimeTableName()
    {
        return fullyQualifiedTable.getAliasedFullyQualifiedTableNameAtRuntime();
    }
    
    public String getFullyQualifiedTableNameAtRuntime()
    {
        return internalAttributes.get(InternalAttribute.ATTR_FULLY_QUALIFIED_TABLE_NAME_AT_RUNTIME);
    }
    
    public String getAliasedFullyQualifiedTableNameAtRuntime()
    {
        return internalAttributes.get(InternalAttribute.ATTR_ALIASED_FULLY_QUALIFIED_TABLE_NAME_AT_RUNTIME);
    }
    
    /**
     * This method can be used to initialize the generators before they will be called.
     * 
     * This method is called after all the setX methods, but before getNumberOfSubtasks(), getGeneratedJavaFiles, and
     * getGeneratedXmlFiles.
     * 
     * @param warnings
     * @param progressCallback
     */
    public abstract void calculateGenerators(List<String> warnings, ProgressCallback progressCallback);
    
    /**
     * This method should return a list of generated Java files related to this table. This list could include various
     * types of model classes, as well as DAO classes.
     * 
     * @return the list of generated Java files for this table
     */
    public abstract List<GeneratedJavaFile> getGeneratedJavaFiles();
    
    /**
     * This method should return a list of generated XML files related to this table. Most implementations will only
     * return one file - the generated SqlMap file.
     * 
     * @return the list of generated XML files for this table
     */
    public abstract List<GeneratedXmlFile> getGeneratedXmlFiles();
    
    /**
     * Denotes whether generated code is targeted for Java version 5.0 or higher.
     * 
     * @return true if the generated code makes use of Java5 features
     */
    public abstract boolean isJava5Targeted();
    
    /**
     * This method should return the number of progress messages that will be send during the generation phase.
     * 
     * @return the number of progress messages
     */
    public abstract int getGenerationSteps();
    
    /**
     * This method exists to give plugins the opportunity to replace the calculated rules if necessary.
     * 
     * @param rules
     */
    public void setRules(Rules rules)
    {
        this.rules = rules;
    }
    
    public TableConfiguration getTableConfiguration()
    {
        return tableConfiguration;
    }
    
    public void setDAOImplementationType(String DAOImplementationType)
    {
        internalAttributes.put(InternalAttribute.ATTR_DAO_IMPLEMENTATION_TYPE, DAOImplementationType);
    }
    
    public void setDAOInterfaceType(String DAOInterfaceType)
    {
        internalAttributes.put(InternalAttribute.ATTR_DAO_INTERFACE_TYPE, DAOInterfaceType);
    }
    
    public void setPrimaryKeyType(String primaryKeyType)
    {
        internalAttributes.put(InternalAttribute.ATTR_PRIMARY_KEY_TYPE, primaryKeyType);
    }
    
    public void setBaseRecordType(String baseRecordType)
    {
        internalAttributes.put(InternalAttribute.ATTR_BASE_RECORD_TYPE, baseRecordType);
    }
    
    public void setRecordWithBLOBsType(String recordWithBLOBsType)
    {
        internalAttributes.put(InternalAttribute.ATTR_RECORD_WITH_BLOBS_TYPE, recordWithBLOBsType);
    }
    
    public void setExampleType(String exampleType)
    {
        internalAttributes.put(InternalAttribute.ATTR_EXAMPLE_TYPE, exampleType);
    }
    
    public void setIbatis2SqlMapPackage(String sqlMapPackage)
    {
        internalAttributes.put(InternalAttribute.ATTR_IBATIS2_SQL_MAP_PACKAGE, sqlMapPackage);
    }
    
    public void setIbatis2SqlMapFileName(String sqlMapFileName)
    {
        internalAttributes.put(InternalAttribute.ATTR_IBATIS2_SQL_MAP_FILE_NAME, sqlMapFileName);
    }
    
    public void setIbatis2SqlMapNamespace(String sqlMapNamespace)
    {
        internalAttributes.put(InternalAttribute.ATTR_IBATIS2_SQL_MAP_NAMESPACE, sqlMapNamespace);
    }
    
    public void setMyBatis3FallbackSqlMapNamespace(String sqlMapNamespace)
    {
        internalAttributes.put(InternalAttribute.ATTR_MYBATIS3_FALLBACK_SQL_MAP_NAMESPACE, sqlMapNamespace);
    }
    
    public void setSqlMapFullyQualifiedRuntimeTableName(String fullyQualifiedRuntimeTableName)
    {
        internalAttributes.put(InternalAttribute.ATTR_FULLY_QUALIFIED_TABLE_NAME_AT_RUNTIME, fullyQualifiedRuntimeTableName);
    }
    
    public void setSqlMapAliasedFullyQualifiedRuntimeTableName(String aliasedFullyQualifiedRuntimeTableName)
    {
        internalAttributes.put(InternalAttribute.ATTR_ALIASED_FULLY_QUALIFIED_TABLE_NAME_AT_RUNTIME, aliasedFullyQualifiedRuntimeTableName);
    }
    
    public String getMyBatis3XmlMapperPackage()
    {
        return internalAttributes.get(InternalAttribute.ATTR_MYBATIS3_XML_MAPPER_PACKAGE);
    }
    
    public void setMyBatis3XmlMapperPackage(String mybatis3XmlMapperPackage)
    {
        internalAttributes.put(InternalAttribute.ATTR_MYBATIS3_XML_MAPPER_PACKAGE, mybatis3XmlMapperPackage);
    }
    
    public String getMyBatis3XmlMapperFileName()
    {
        return internalAttributes.get(InternalAttribute.ATTR_MYBATIS3_XML_MAPPER_FILE_NAME);
    }
    
    public void setMyBatis3XmlMapperFileName(String mybatis3XmlMapperFileName)
    {
        internalAttributes.put(InternalAttribute.ATTR_MYBATIS3_XML_MAPPER_FILE_NAME, mybatis3XmlMapperFileName);
    }
    
    public String getMyBatis3JavaMapperType()
    {
        return internalAttributes.get(InternalAttribute.ATTR_MYBATIS3_JAVA_MAPPER_TYPE);
    }
    
    public void setMyBatis3JavaMapperType(String mybatis3JavaMapperType)
    {
        internalAttributes.put(InternalAttribute.ATTR_MYBATIS3_JAVA_MAPPER_TYPE, mybatis3JavaMapperType);
    }
    
    public String getMyBatis3SqlProviderType()
    {
        return internalAttributes.get(InternalAttribute.ATTR_MYBATIS3_SQL_PROVIDER_TYPE);
    }
    
    public void setMyBatis3SqlProviderType(String mybatis3SqlProviderType)
    {
        internalAttributes.put(InternalAttribute.ATTR_MYBATIS3_SQL_PROVIDER_TYPE, mybatis3SqlProviderType);
    }
    
    public TargetRuntime getTargetRuntime()
    {
        return targetRuntime;
    }
    
    public boolean isImmutable()
    {
        Properties properties;
        
        if (tableConfiguration.getProperties().containsKey(PropertyRegistry.ANY_IMMUTABLE))
        {
            properties = tableConfiguration.getProperties();
        }
        else
        {
            properties = context.getJavaModelGeneratorConfiguration().getProperties();
        }
        
        return isTrue(properties.getProperty(PropertyRegistry.ANY_IMMUTABLE));
    }
    
    public boolean isConstructorBased()
    {
        if (isImmutable())
        {
            return true;
        }
        
        Properties properties;
        
        if (tableConfiguration.getProperties().containsKey(PropertyRegistry.ANY_CONSTRUCTOR_BASED))
        {
            properties = tableConfiguration.getProperties();
        }
        else
        {
            properties = context.getJavaModelGeneratorConfiguration().getProperties();
        }
        
        return isTrue(properties.getProperty(PropertyRegistry.ANY_CONSTRUCTOR_BASED));
    }
    
    /**
     * Should return true if an XML generator is required for this table. This method will be called during validation
     * of the configuration, so it should not rely on database introspection. This method simply tells the validator if
     * an XML configuration is normally required for this implementation.
     * 
     * @return
     */
    public abstract boolean requiresXMLGenerator();
    
    public Context getContext()
    {
        return context;
    }
}
